package ru.ksu.ktk.diploma.sdi.search;
import static java.lang.Class.forName;
import gate.util.GateException;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.xmldb.api.DatabaseManager;
import org.xmldb.api.base.Database;
import org.xmldb.api.base.XMLDBException;
import org.xmldb.api.modules.XMLResource;
import org.xmldb.api.modules.XPathQueryService;

import ru.edu.niimm.mapping.linguistic.NLP;

import com.hp.hpl.jena.ontology.Individual;
import com.hp.hpl.jena.ontology.OntClass;
import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.ontology.OntModelSpec;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.util.iterator.ExtendedIterator;

public class SearchImpl implements Search
{
    static XPathQueryService xpathToCollection;
    static Database database;
    static org.xmldb.api.base.Collection dataCollection;
    protected OntModel rusaat;
    
    private NLP nlpService;
	private String pathToIndex;

    public SearchImpl(){
    }

    public static void main(String[] args)
    {	
    	SearchImpl engine = new SearchImpl();

        engine.init();

        String qString = "Копье";
        
        Map<List<Individual>, OntModel> queryResults = engine.search(qString);

        System.out.println("Result set URL list:");
        for(OntModel o : queryResults.values())
        {
            for(String s :engine.listResultURLs(o))
            {
                System.out.println(s);
            }
        }
    }
    
    /* (non-Javadoc)
	 * @see Search#search(java.lang.String)
	 */
    public Map<List<Individual>, OntModel> search(String qString){
    	
        List<Individual> terms = prepareQuery(qString);
        System.out.println("query \"" + qString + "\" OK.");

        Map<List<Individual>, OntModel> queryResults = new HashMap<List<Individual>, OntModel>();

        queryResults.put(terms, executeFastQuery(terms));

        System.out.println("query execution OK.");

        storeQueryResult(queryResults);

        System.out.println("query results storing OK.");
        
        return queryResults;
    }
    
    public List<String> searchForUrls(String qString){
    	LinkedList<String> result = new LinkedList<String>();
    	for(OntModel curOnt : search(qString).values())
    		result.addAll(listResultURLs(curOnt));
    	return result;
    }

    public void init()
    {
        existInit();
        System.out.println("Exist init OK.");
        loadRUSAAT();
        System.out.println("RUSAAT OK.");
    }

    // инициируем СУБД eXist
    private static void existInit()
    {
        String existDriver = "org.exist.xmldb.DatabaseImpl";
        try {
            Class cl = forName(existDriver);
            database = (Database)cl.newInstance();
            DatabaseManager.registerDatabase(database);
            dataCollection = DatabaseManager.getCollection(
                    "xmldb:exist://localhost:8080/exist/xmlrpc/db/mom");
            xpathToCollection = (XPathQueryService) dataCollection.getService("XPathQueryService", "1.0");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (XMLDBException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }

    // загрузка тезауруса ААТ
    public void loadRUSAAT()
    {
        this.rusaat = ModelFactory.createOntologyModel(OntModelSpec.OWL_LITE_MEM);
        this.rusaat.read(SearchConstants.existCollectionURI +
                SearchConstants.existThesaurusRUSAAT, "RDF/XML-ABBREV");
    }

    // предобработка текста запроса
    public ArrayList<Individual> prepareQuery(String query)
    {
        ArrayList<Individual> result = new ArrayList<Individual>();
        query = query.toUpperCase().replaceAll("[^А-Я]", " ").trim();
        if(!query.equalsIgnoreCase(""))
        {
            try {
                String ontPath = SearchConstants.existCollectionURI +
                        SearchConstants.existThesaurusRUSAAT;
                //TODO:RGareev: remove hard-coded values
                List<String> res = nlpService.execOnFieldIndex(
                		"1", ontPath, new String[]{query}, "ru");
                if(res != null && res.size() > 0)
                {
                    for(String descriptor : res)
                    {
                        Individual d = this.rusaat.getIndividual(SearchConstants.rusaatNS + descriptor);
                        result.add(d);
                    }
                }
            } catch (IOException e) {
            	//TODO:RGareev: exception handling approach
                throw new RuntimeException(e);
            } catch (GateException e) {
            	throw new RuntimeException(e);
			}
        }
        return result;
    }

    // поиск, основанный на индексе
    public OntModel executeFastQuery(List<Individual> descriptors)
    {
        ArrayList<OntModel> models = retrieveModelsFromIndex(descriptors);
        return applyFormulaOnModels(models, "INTERSECT");
    }

    // извлекаем онтологии-окрестности из индекса
    protected ArrayList<OntModel> retrieveModelsFromIndex(List<Individual> descriptors)
    {
        ArrayList<OntModel> res = new ArrayList<OntModel>(descriptors.size());
        for(Individual i : descriptors )
        {
            try {
                File f = new File(pathToIndex + i.getLocalName() + ".owl");
                if(f.exists())
                {
                    OntModel o = ModelFactory.createOntologyModel(OntModelSpec.OWL_LITE_MEM);
                    //TODO:RGareev: change to InputStream usage according to Jena javadocs
                    FileReader fileReader = new FileReader(f);
                    try{
                    	o.read(fileReader, f.toURI().toURL().toString(), "RDF/XML-ABBREV");
                    }finally{
                    	fileReader.close();
                    }
                    res.add(o);
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            } catch (MalformedURLException e) {
                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            } catch (IOException e){
            	throw new RuntimeException(e);
            }
        }
        return res;
    }

    /* (non-Javadoc)
	 * @see Search#listResultURLs(java.lang.String)
	 */
    public List<String> listResultURLs(String resultsURL)
    {
        OntModel o = ModelFactory.createOntologyModel(OntModelSpec.OWL_LITE_MEM);
        o.read(resultsURL, "RDF/XML-ABBREV");
        return listResultURLs(o);
    }

    /* (non-Javadoc)
	 * @see Search#listResultURLs(com.hp.hpl.jena.ontology.OntModel)
	 */
    public List<String> listResultURLs(OntModel resultsModel)
    {
        ArrayList<String> res = new ArrayList<String>();
        OntClass E42 = this.rusaat.getOntClass(SearchConstants.crmNS + "E42_");
        ExtendedIterator ie = resultsModel.listIndividuals(E42);
        while(ie.hasNext())
        {
            Individual i = (Individual)ie.next();
            if(i.getURI().startsWith(SearchConstants.vrmNS))
            {
                res.add(SearchConstants.vrmURLPrefix + i.getLabel("ru"));
            }
            else if (i.getURI().startsWith(SearchConstants.emkuNS))
            {
                res.add(SearchConstants.emkuURLPrefix + i.getLabel("ru"));
            }
        }
        return res;
    }

    //--------------------------------------------------------------------------------------------

    // применение опрератора "пересечения" над списком онтологий
    private Model multipleIntersection(OntModel currentModel, List<OntModel> modelsTail)
    {
        if(modelsTail.size() > 1)
        {
            return currentModel.getBaseModel().intersection(
                    (multipleIntersection(
                            modelsTail.get(0), modelsTail.subList(1, modelsTail.size()))));
        }
        else
        {
            if (modelsTail.size() == 1)
            {
                return currentModel.getBaseModel().intersection(modelsTail.get(0).getBaseModel());
            }
            else
            {
                return currentModel;
            }
        }
    }

    // применение опрератора "объединения" над списком онтологий
    private Model multipleJoin(OntModel currentModel, List<OntModel> modelsTail)
    {
        if(modelsTail.size() > 1)
        {
            return currentModel.getBaseModel().intersection(
                    (multipleJoin(
                            modelsTail.get(0), modelsTail.subList(1, modelsTail.size()))));
        }
        else
        {
            if (modelsTail.size() == 1)
            {
                return currentModel.getBaseModel().union(modelsTail.get(0).getBaseModel());
            }
            else
            {
                return currentModel;
            }
        }
    }

    // сохраняем результат поиска в базу
    protected void storeQueryResult(Map<List<Individual>, OntModel> queryResults)
    {

        for(List<Individual> key : queryResults.keySet())
        {
            String filename = "TMP/";
            for(Individual i : key)
            {
                filename = filename + i.getLocalName() + "_";
            }

            File f = new File(filename + ".owl");
            try {
                FileWriter fw = new FileWriter(f);
                OntModel res = queryResults.get(key);
                res.setNsPrefix("rusaat", SearchConstants.rusaatNS);
                res.setNsPrefix("crm", SearchConstants.crmNS);
                res.write(fw, "RDF/XML-ABBREV");
                fw.close();
                System.out.println( countQueryResultItems(res) + " objects found" );

                XMLResource   document = (XMLResource)dataCollection.
                        getChildCollection("query").createResource(f.getName(), "XMLResource");
                document.setContent(f);
                System.out.print("storing document " + document.getId() + "...");
                dataCollection.getChildCollection("query").storeResource(document);
            } catch (XMLDBException e) {
                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.

            } catch (IOException e) {
                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            }


            System.out.println("ok.");
            f.delete();
        }
    }

    protected int countQueryResultItems(OntModel res)
    {
        OntClass E42 = this.rusaat.getOntClass(SearchConstants.crmNS + "E42_");
        ExtendedIterator ie = res.listIndividuals(E42);
        return ie.toList().size();
    }

    // выполнение формулы заданного типа над списком онтологий
    protected OntModel applyFormulaOnModels(ArrayList<OntModel> models, String formulaType)
    {
        OntModel resIntersected = ModelFactory.createOntologyModel();
        if(formulaType.equals("INTERSECT"))
        {
            if(models.size() > 2)
            {
                resIntersected = ModelFactory.createOntologyModel(OntModelSpec.OWL_LITE_MEM,
                        multipleIntersection(models.get(0), models.subList(1, models.size())));
            }
            else
            if (models.size() == 2)
            {
                resIntersected = ModelFactory.createOntologyModel(OntModelSpec.OWL_LITE_MEM,
                        models.get(0).getBaseModel().intersection(models.get(1).getBaseModel()));
            }
            else if (models.size() == 1)
            {
                resIntersected = models.get(0);
            }
        }

        if(formulaType.equals("JOIN"))
        {
            if(models.size() > 2)
            {
                resIntersected = ModelFactory.createOntologyModel(OntModelSpec.OWL_LITE_MEM,
                        multipleJoin(models.get(0), models.subList(1, models.size())));
            }
            else
            if (models.size() == 2)
            {
                resIntersected = ModelFactory.createOntologyModel(OntModelSpec.OWL_LITE_MEM,
                        models.get(0).getBaseModel().union(models.get(1).getBaseModel()));
            }
            else if (models.size() == 1)
            {
                resIntersected = models.get(0);
            }
        }

        return resIntersected;
    }

	/**
	 * @return the nlpService
	 */
	public NLP getNlpService() {
		return nlpService;
	}

	/**
	 * @param nlpService the nlpService to set
	 */
	public void setNlpService(NLP nlpService) {
		this.nlpService = nlpService;
	}

	/**
	 * @param pathToIndex the pathToIndex to set
	 */
	public void setPathToIndex(String pathToIndex) {
		this.pathToIndex = pathToIndex;
	}
}